package com.db.server;

import java.util.logging.Logger;

import com.db.data.Order;
import com.db.data.Price;
import com.db.protocol.tags.OrderSide;
import com.db.protocol.tags.OrderType;
import com.db.server.data.ServerOrder;

public class MatchingEngine {
	
	private static Logger log = Logger.getLogger(MatchingEngine.class.getName());
	
	public interface FilledListener {
		
		void filled(String clientID, Order order, int quantity, Price price, String otherClientID);
		
		void partiallyFilled(String clientID, Order order, int quantity, Price price, String otherClientID);
		
	}
	
	private Exchange exchange;
	
	private FilledListener listener;
	
	public MatchingEngine(Exchange exchange, FilledListener listener) {
		this.exchange = exchange;
		this.listener = listener;
	}

	public ServerOrder matchingOrder(ServerOrder order) {
		log.info(String.format("Matching Order: [%s]. Ticket: [%s]. Qty: [%d]. Price: [%s]. Side: [%s]",
									order.getOrderID(), order.getTicketSymbol(),
									order.getQuantity(), order.getPrice().toString(), order.getSide().toString()));
		if (order.getSide() == OrderSide.BUY) {
			if (exchange.hasBuyMarketOrder(order.getTicketSymbol())) {
				return order;
			}
			
			if (order.getType() == OrderType.LIMIT) {
				return matchingBuyOrder(order);
			} else {
				return matchingBuyMarketOrder(order);
			}
		} else {
			if (exchange.hasSellMarketOrder(order.getTicketSymbol())) {
				return order;
			}
			
			if (order.getType() == OrderType.LIMIT) {
				return matchingSellOrder(order);
			} else {
				return matchingSellMarketOrder(order);
			}
		}
	}
	
	protected ServerOrder matchingBuyOrder(ServerOrder order) {
		ServerOrder bestSellOrder;
		while ((bestSellOrder = exchange.bestSellOrder(order.getTicketSymbol())) != null) {
			int quantity = Math.min(bestSellOrder.getQuantity(), order.getQuantity());
			Price price = bestSellOrder.getPrice();
			if (price.compareTo(order.getPrice()) < 0) {
				break;
			}
			
			order.partiallyFilling(quantity);
			bestSellOrder.partiallyFilling(quantity);
			
			if (bestSellOrder.filled()) {
				filled(bestSellOrder, quantity, price, order.getClientID());
				exchange.remove(bestSellOrder);
			} else {
				partiallyFilled(bestSellOrder, quantity, price, order.getClientID());
			}
			
			if (order.filled()) {
				filled(order, quantity, price, bestSellOrder.getClientID());
				return order;
			} else {
				partiallyFilled(order, quantity, price, bestSellOrder.getClientID());
			}			
		}
		
		ServerOrder firstSellMatketOrder;
		Price price = order.getPrice();
		while ((firstSellMatketOrder = exchange.fisrtSellMarketOrder(order.getTicketSymbol())) != null) {
			int quantity = Math.min(firstSellMatketOrder.getQuantity(), order.getQuantity());
			
			order.partiallyFilling(quantity);
			firstSellMatketOrder.partiallyFilling(quantity);
			
			if (firstSellMatketOrder.filled()) {
				filled(firstSellMatketOrder, quantity, price, order.getClientID());
				exchange.remove(firstSellMatketOrder);
			} else {
				partiallyFilled(firstSellMatketOrder, quantity, price, order.getClientID());
			}
			
			if (order.filled()) {
				filled(order, quantity, price, firstSellMatketOrder.getClientID());
				return order;
			} else {
				partiallyFilled(order, quantity, price, firstSellMatketOrder.getClientID());
			}
		}
		return order;
	}

	protected ServerOrder matchingBuyMarketOrder(ServerOrder order) {
		ServerOrder bestSellOrder;
		while ((bestSellOrder = exchange.bestSellOrder(order.getTicketSymbol())) != null) {
			int quantity = Math.min(bestSellOrder.getQuantity(), order.getQuantity());
			Price price = bestSellOrder.getPrice();
			
			order.partiallyFilling(quantity);
			bestSellOrder.partiallyFilling(quantity);
			
			if (bestSellOrder.filled()) {
				filled(bestSellOrder, quantity, price, order.getClientID());
				exchange.remove(bestSellOrder);
			} else {
				partiallyFilled(bestSellOrder, quantity, price, order.getClientID());
			}
			
			if (order.filled()) {
				filled(order, quantity, price, bestSellOrder.getClientID());
				return order;
			} else {
				partiallyFilled(order, quantity, price, bestSellOrder.getClientID());
			}			
		}
		return order;
	}
	
	protected ServerOrder matchingSellOrder(ServerOrder order) {
		ServerOrder bestBuyOrder;
		while ((bestBuyOrder = exchange.bestBuyOrder(order.getTicketSymbol())) != null) {
			int quantity = Math.min(bestBuyOrder.getQuantity(), order.getQuantity());
			Price price = bestBuyOrder.getPrice();
			if (price.compareTo(order.getPrice()) > 0) {
				break;
			}
			
			order.partiallyFilling(quantity);
			bestBuyOrder.partiallyFilling(quantity);
			
			if (bestBuyOrder.filled()) {
				filled(bestBuyOrder, quantity, price, order.getClientID());
				exchange.remove(bestBuyOrder);
			} else {
				partiallyFilled(bestBuyOrder, quantity, price, order.getClientID());
			}
			
			if (order.filled()) {
				filled(order, quantity, price, bestBuyOrder.getClientID());
				return order;
			} else {
				partiallyFilled(order, quantity, price, bestBuyOrder.getClientID());
			}			
		}
		
		ServerOrder firstBuyMatketOrder;
		Price price = order.getPrice();
		while ((firstBuyMatketOrder = exchange.fisrtBuyMarketOrder(order.getTicketSymbol())) != null) {
			int quantity = Math.min(firstBuyMatketOrder.getQuantity(), order.getQuantity());
			
			order.partiallyFilling(quantity);
			firstBuyMatketOrder.partiallyFilling(quantity);
			
			if (firstBuyMatketOrder.filled()) {
				filled(firstBuyMatketOrder, quantity, price, order.getClientID());
				exchange.remove(firstBuyMatketOrder);
			} else {
				partiallyFilled(firstBuyMatketOrder, quantity, price, order.getClientID());
			}
			
			if (order.filled()) {
				filled(order, quantity, price, firstBuyMatketOrder.getClientID());
				return order;
			} else {
				partiallyFilled(order, quantity, price, firstBuyMatketOrder.getClientID());
			}
		}
		return order;
	}
	
	protected ServerOrder matchingSellMarketOrder(ServerOrder order) {
		ServerOrder bestBuyOrder;
		while ((bestBuyOrder = exchange.bestBuyOrder(order.getTicketSymbol())) != null) {
			int quantity = Math.min(bestBuyOrder.getQuantity(), order.getQuantity());
			Price price = bestBuyOrder.getPrice();
			
			order.partiallyFilling(quantity);
			bestBuyOrder.partiallyFilling(quantity);
			
			if (bestBuyOrder.filled()) {
				filled(bestBuyOrder, quantity, price, order.getClientID());
				exchange.remove(bestBuyOrder);
			} else {
				partiallyFilled(bestBuyOrder, quantity, price, order.getClientID());
			}
			
			if (order.filled()) {
				filled(order, quantity, price, bestBuyOrder.getClientID());
				return order;
			} else {
				partiallyFilled(order, quantity, price, bestBuyOrder.getClientID());
			}			
		}
		return order;
	}
	
	
	private void filled(ServerOrder order, int quantity, Price price, String otherClientID) {
		log.info("Filled [" + order.getOrderID() + "] with [" + otherClientID + "]. Qty = " + quantity + "; Price = " + price);
		if (listener != null) {
			listener.filled(order.getClientID(), order, quantity, price, otherClientID);
		}
	}
	
	private void partiallyFilled(ServerOrder order, int quantity, Price price, String otherClientID) {
		log.info("Partially filled [" + order.getOrderID() + "] with [" + otherClientID + "]. Qty = " + quantity + "; Price = " + price + ". Left Qty = " + order.getCountOfNotFilled());
		if (listener != null) {
			listener.partiallyFilled(order.getClientID(), order, quantity, price, otherClientID);
		}
	}
	
}
